aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)/[username]/(profile)
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/(app)/[username]/(profile)')
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte3
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.server.js29
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte229
-rw-r--r--src/routes/(app)/[username]/(profile)/messages/+page.server.js16
-rw-r--r--src/routes/(app)/[username]/(profile)/messages/+page.svelte123
-rw-r--r--src/routes/(app)/[username]/(profile)/request/+page.server.js6
6 files changed, 200 insertions, 206 deletions
diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte
index dfa25b78..133930b1 100644
--- a/src/routes/(app)/[username]/(profile)/+layout.svelte
+++ b/src/routes/(app)/[username]/(profile)/+layout.svelte
@@ -4,7 +4,6 @@
import { Icon } from '$comp';
import { t } from '$lib/translations';
import { sign, send } from '$lib/nostr';
- import { v4 } from 'uuid';
export let data;
let events, user, subject, src, text;
@@ -111,7 +110,7 @@
{/if}
<div class="w-full flex">
- <a href={`/${subject.pubkey}/messages`} class="mx-auto">
+ <a href={`/${subject.username}/messages`} class="mx-auto">
<button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60">
<div class="mx-auto flex">
<Icon icon="support" style="mr-2 my-auto" />
diff --git a/src/routes/(app)/[username]/(profile)/+page.server.js b/src/routes/(app)/[username]/(profile)/+page.server.js
index c68cfecc..d6cf3984 100644
--- a/src/routes/(app)/[username]/(profile)/+page.server.js
+++ b/src/routes/(app)/[username]/(profile)/+page.server.js
@@ -1,22 +1,35 @@
import { auth, get } from '$lib/utils';
export async function load({ cookies, params, parent, url }) {
- let { subject } = await parent();
+ let { user, subject } = await parent();
let { pubkey } = subject;
+ let { since = 0 } = params;
- let events = [];
+ let messages, invoices, sent, received;
+ messages = invoices = sent = received = [];
+
+ if (user) {
+ try {
+ messages = await get(`/${user.pubkey}/${since}/messages`);
+ messages = messages.sort((a, b) => b.created_at - a.created_at);
+ } catch (e) {
+ console.log(`failed to fetch nostr messages`, e);
+ }
+
+ ({ invoices, sent, received } = await get('/requests', auth(cookies)));
+ }
+
+ let notes = [];
try {
- events = await get(`/${pubkey}/notes`);
+ notes = await get(`/${pubkey}/notes`);
} catch (e) {
- console.log(`failed to fetch nostr events for ${pubkey}`, e);
+ console.log(`failed to fetch nostr notes for ${pubkey}`, e);
}
- events.map((e) => {
+ notes.map((e) => {
e.seen = e.created_at;
});
- let { invoices, sent, received } = await get('/requests', auth(cookies));
-
- return { invoices, events, sent, received };
+ return { invoices, messages, notes, sent, received };
}
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index baab8ba1..168556e4 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -1,76 +1,197 @@
<script>
- import { Avatar, Balance, Feed } from '$comp';
+ import { onMount } from 'svelte';
+ import { Icon, Avatar, Balance, Feed } from '$comp';
import { t } from '$lib/translations';
import { f, sat, sats } from '$lib/utils';
+ import { sign, send, encrypt, decrypt } from '$lib/nostr';
+ import { event as e, password } from '$lib/store';
+ import { browser } from '$app/environment';
export let data;
- let { events, invoices, sent, received, subject, user } = data;
- console.log(invoices);
+ let { messages, notes, invoices, sent, received, subject, user } = data;
+ $: refresh(data);
+ let refresh = (d) => ({ messages, notes, invoices, sent, received, subject, user } = d);
+
+ let keys = new Set();
+ let latest = [];
+ let i = 0;
+ let ready;
+
+ e.subscribe(async (event) => {
+ if (!(event && ready)) return;
+ if (event.recipient.id === user?.id && !~latest.findIndex((m) => m.id === event.id)) {
+ event.content = await decrypt({ event, user });
+
+ let i = latest.findIndex((m) => m.pubkey === event.pubkey);
+ let popped;
+ if (~i) popped = latest.splice(i, 1);
+ else popped = latest.pop();
+
+ latest.unshift(event);
+ latest = latest;
+ }
+ });
+
+ $: initialize($password);
+ let initialize = async (p) => {
+ ready = false;
+ if (!p) return;
+ while (latest.length < 3 && i < messages.length) {
+ let event = messages[i];
+ let k = event.author.id === user.id ? event.recipient.pubkey : event.author.pubkey;
+ if (!keys.has(k)) {
+ keys.add(k);
+ event.content = await decrypt({ event, user });
+ if (event.content) latest.push(event);
+ }
+ i++;
+ }
+
+ latest = latest;
+ ready = true;
+ };
</script>
-<div class="flex justify-center lg:justify-start">
+<div class="space-y-8">
{#if user?.id === subject.id}
- <Balance {user} />
- {/if}
-</div>
+ <div>
+ <div class="flex justify-center lg:justify-start">
+ <Balance {user} />
+ </div>
+ {#if !user.balance}
+ <div class="space-y-8 mt-8">
+ <p class="text-secondary text-lg">
+ Welcome! Your account balance is empty. You can start by making a deposit or by asking
+ someone to pay you.
+ </p>
-<div>
- {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }}
- {@const requested = user.username === requester?.username}
- <a href={`/${recipient.username}/invoice/${hash}` + (requested ? '/tip' : '')}>
- <div class="border-b p-2 last:border-b-0 hover:bg-gray-100">
- <div class="flex gap-2">
- <div>
- {#if requested}
- <div class="flex">
- <Avatar user={recipient} size={20} />
- <div class="my-auto text-left">
- <p class="text-secondary">{$t('payments.invoiceFrom')}</p>
- <p class="ml-1 text-lg break-words">{recipient.username}</p>
+ <div class="flex gap-4 justify-center w-full">
+ <a href={`/${user.username}/receive`}>
+ <button
+ class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white"
+ >
+ <div class="mx-auto flex">
+ <Icon icon="numpad" style="my-auto h-6 mr-2 invert" />
+ <div class="my-auto mt-1">Request Payment</div>
</div>
+ </button>
+ </a>
+
+ {#if user.eligible}
+ <a href={`/buy`}>
+ <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60">
+ <div class="mx-auto flex">
+ <!-- <Icon icon="plus" style="my-auto h-6 mr-2" /> -->
+
+ <img
+ src="/images/bitcoin.svg"
+ class="w-8 border-4 border-transparent mr-2"
+ alt="Bitcoin"
+ />
+ <div class="my-auto mt-1">Buy Bitcoin</div>
+ </div>
+ </button>
+ </a>
+ {/if}
+ </div>
+ </div>
+ {/if}
+ </div>
+ {/if}
+
+ {#if invoices.length}
+ <div>
+ <h1 class="text-secondary text-xl">Invoices</h1>
+ {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }}
+ {@const requested = user.username === requester?.username}
+ <a href={`/${recipient.username}/invoice/${hash}` + (requested ? '/tip' : '')}>
+ <div class="border-b p-2 last:border-b-0 hover:bg-gray-100">
+ <div class="flex gap-2">
+ <div>
+ {#if requested}
+ <div class="flex">
+ <Avatar user={recipient} size={20} />
+ <div class="my-auto text-left">
+ <p class="text-secondary">{$t('payments.invoiceFrom')}</p>
+ <p class="ml-1 text-lg break-words">{recipient.username}</p>
+ </div>
+ </div>
+ {:else}
+ <div class="flex">
+ <Avatar user={requester} size={20} />
+ <div class="my-auto text-left">
+ <p class="text-secondary">{$t('payments.awaiting')}</p>
+ <p class="ml-1 text-lg break-words">{requester.username}</p>
+ </div>
+ </div>
+ {/if}
</div>
- {:else}
- <div class="flex">
- <Avatar user={requester} size={20} />
- <div class="my-auto text-left">
- <p class="text-secondary">{$t('payments.awaiting')}</p>
- <p class="ml-1 text-lg break-words">{requester.username}</p>
+ <div class="whitespace-nowrap my-auto ml-auto flex gap-2">
+ <div class="font-bold">
+ {f(amount * (rate / sats), currency)}
</div>
+
+ <div class="text-secondary">{sat(amount)}</div>
</div>
- {/if}
- </div>
- <div class="whitespace-nowrap my-auto ml-auto flex gap-2">
- <div class="font-bold">
- {f(amount * (rate / sats), currency)}
</div>
-
- <div class="text-secondary">{sat(amount)}</div>
</div>
- </div>
- </div>
- </a>
- {/each}
+ </a>
+ {/each}
+ </div>
+ {/if}
+ {#if received.length}
+ <div>
+ <h1 class="text-secondary text-xl">Requests</h1>
- {#each received as { id, invoice, memo, requester: r }}
- <a href={`/${user.username}/receive/${id}`}>
- <div class="border-b p-2 last:border-b-0 hover:bg-gray-100">
- <div class="flex">
- <div>
+ {#each received as { id, invoice, memo, requester: r }}
+ <a href={`/${user.username}/receive/${id}`}>
+ <div class="border-b p-2 last:border-b-0 hover:bg-gray-100">
<div class="flex">
- <Avatar user={r} size={20} />
- <div class="my-auto text-left">
- <p class="ml-1 text-lg break-words">{r.username}</p>
- <p class="ml-1 text-secondary">{memo}</p>
+ <div>
+ <div class="flex">
+ <Avatar user={r} size={20} />
+ <div class="my-auto text-left">
+ <p class="ml-1 text-lg break-words">{r.username}</p>
+ <p class="ml-1 text-secondary">{memo}</p>
+ </div>
+ </div>
+ </div>
+ <div class="whitespace-nowrap my-auto ml-auto flex gap-2">
+ <button class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full"
+ >Invoice</button
+ >
</div>
</div>
</div>
- <div class="whitespace-nowrap my-auto ml-auto flex gap-2">
- <button class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full"
- >Invoice</button
- >
+ </a>
+ {/each}
+ </div>
+ {/if}
+
+ {#if latest.length && user?.id === subject.id}
+ <div class="relative">
+ {#each latest as { content, pubkey, author, recipient }}
+ {@const user = author.id === user.id ? recipient : author}
+ <a href={`/${user.username}/messages`}>
+ <div class="flex hover:bg-gray-100 p-4 rounded-2xl">
+ <div class="my-auto">
+ <Avatar {user} size={'20'} disabled={true} />
+ </div>
+ <div class="my-auto truncate">
+ <div class="my-auto ml-1 text-lg font-bold">{user.username}</div>
+ <div class="my-auto ml-1 text-secondary text-lg truncate">
+ {content}
+ </div>
+ </div>
</div>
- </div>
- </div>
- </a>
- {/each}
+ </a>
+ {/each}
+ </div>
+ {/if}
</div>
+
+<style>
+ h1 {
+ @apply mb-2;
+ }
+</style>
diff --git a/src/routes/(app)/[username]/(profile)/messages/+page.server.js b/src/routes/(app)/[username]/(profile)/messages/+page.server.js
deleted file mode 100644
index 63116099..00000000
--- a/src/routes/(app)/[username]/(profile)/messages/+page.server.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { get } from '$lib/utils';
-
-export async function load({ params, parent, url }) {
- let { user, subject } = await parent();
- let { since = 0 } = params;
-
- let messages = [];
-
- try {
- messages = await get(`/nostr/${user.pubkey}/${subject.pubkey}/${since}/messages`);
- } catch (e) {
- console.log(`failed to fetch nostr messages`, e);
- }
-
- return { newMessages: messages };
-}
diff --git a/src/routes/(app)/[username]/(profile)/messages/+page.svelte b/src/routes/(app)/[username]/(profile)/messages/+page.svelte
deleted file mode 100644
index db7b1ced..00000000
--- a/src/routes/(app)/[username]/(profile)/messages/+page.svelte
+++ /dev/null
@@ -1,123 +0,0 @@
-<script>
- import { browser } from '$app/environment';
- import { t } from '$lib/translations';
- import { format, parseISO } from 'date-fns';
- import { scale } from 'svelte/transition';
- import { enhance } from '$app/forms';
- import { Icon } from '$comp';
- import { back, fail, focus } from '$lib/utils';
- import { sign, send, encrypt, decrypt } from '$lib/nostr';
- import { messages, password } from '$lib/store';
- import { tick, onMount } from 'svelte';
- import { calculateId } from 'nostr';
-
- export let data;
-
- let { newMessages, subject, user } = data;
- let input, pane;
-
- $: initialize($password);
- let initialize = async (p) => {
- if (!p) return;
- $messages[user.pubkey] = $messages[user.pubkey] || {};
- $messages[user.pubkey][subject.pubkey] = $messages[user.pubkey][subject.pubkey] || {};
-
- for (let m of newMessages) {
- if (!$messages[user.pubkey][subject.pubkey][m.id]) {
- m.message = await decrypt({ event: m, user });
- $messages[user.pubkey][subject.pubkey][m.id] = m;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
- }
- }
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
- };
-
- $: events =
- ($messages[user.pubkey] &&
- $messages[user.pubkey][subject.pubkey] &&
- Object.values($messages[user.pubkey][subject.pubkey]).sort(
- (a, b) => a.created_at - b.created
- )) ||
- [];
-
- let sent, submitting, message;
- let submit = async () => {
- submitting = true;
-
- let event = {
- pubkey: user.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- kind: 4,
- tags: [['p', subject.pubkey]]
- };
-
- event.message = message;
- event.id = await calculateId(event);
- $messages[user.pubkey][subject.pubkey][event.id] = event;
- $messages = $messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
-
- try {
- event.content = await encrypt({ message, recipient: subject.pubkey, user });
- await sign({ event, user });
- await send(event);
-
- sent = true;
- } catch (e) {
- fail('Failed to send message');
- delete $messages[user.pubkey][subject.pubkey][event.id];
- $messages = $messages;
- }
-
- message = '';
- submitting = false;
- };
-</script>
-
-<div class="container max-w-lg px-4 mx-auto mt-10 space-y-5">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
- {$t('payments.sendMessage')}
- </h1>
-
- <div class="max-h-[calc(50vh)] overflow-y-scroll p-4" bind:this={pane}>
- {#each events as data}
- {@const ours = data.pubkey === user.pubkey}
- {@const theirs = !ours}
- <div class="rounded-2xl p-4 max-w-[300px] mb-1 text-md" class:ours class:theirs>
- {#if data.message}
- {data.message}
- {:else}
- -
- {/if}
- </div>
- <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
- {format(new Date(data.created_at * 1000), 'MMM d')},
- {format(new Date(data.created_at * 1000), 'h:mm aa')}
- </div>
- {/each}
- </div>
-
- <form method="POST" class="space-y-5" on:submit|preventDefault={submit}>
- <input type="hidden" name="requester_id" value={user.id} />
- <input type="hidden" name="recipient" value={subject.username} />
-
- <input
- use:focus
- name="message"
- placeholder={$t('payments.sendMessage')}
- class="w-full p-4 border rounded-xl"
- bind:value={message}
- bind:this={input}
- />
- </form>
-</div>
-
-<style>
- .ours {
- @apply bg-gray-100 rounded-br-none ml-auto;
- }
-
- .theirs {
- @apply bg-gray-200 rounded-bl-none;
- }
-</style>
diff --git a/src/routes/(app)/[username]/(profile)/request/+page.server.js b/src/routes/(app)/[username]/(profile)/request/+page.server.js
index 2c1d6bde..e7ad6a8f 100644
--- a/src/routes/(app)/[username]/(profile)/request/+page.server.js
+++ b/src/routes/(app)/[username]/(profile)/request/+page.server.js
@@ -1,15 +1,15 @@
import { redirect } from '@sveltejs/kit';
-import { auth, post } from '$lib/utils';
+import { fd, auth, post } from '$lib/utils';
export const actions = {
create: async ({ cookies, params, request, url }) => {
- let form = Object.fromEntries(await request.formData());
+ let form = await fd(request);
let { id, requester } = await post('/requests', form, auth(cookies));
throw redirect(307, `/${params.username}/request/${id}`);
},
delete: async ({ cookies, request }) => {
- let form = Object.fromEntries(await request.formData());
+ let form = await fd(request);
let r = await post('/requests/delete', form, auth(cookies));
}
};